package jwt

import (
	"crypto/rsa"
	"encoding/base64"
	"encoding/json"
	"errors"
	"git.myscrm.cn/golang/stark/v4"
	"github.com/dgrijalva/jwt-go"
	"github.com/gin-gonic/gin"
	"math/big"
	"net/http"
	"reflect"
	"strings"
)

// JwtOmp 定义一个jwt对象
type JwtOmp struct {
	Rsakeys       map[string]*rsa.PublicKey
	Aud           string
	Iss           string
	PublishKeyUrl string
}

func NewJwtOmp() *JwtOmp {
	return &JwtOmp{
		Aud:           stark.Configer.(stark.ApolloConfigerIface).GetStringValue("omp_token_aud", ""),        //受众
		Iss:           stark.Configer.(stark.ApolloConfigerIface).GetStringValue("omp_token_iss", ""),        //签发人
		PublishKeyUrl: stark.Configer.(stark.ApolloConfigerIface).GetStringValue("omp_token_public_key", ""), //公钥地址
	}
}

// GetPublicKeys 获取公钥 公钥地址 https://auth.mingyuanyun.com/.well-known/openid-configuration/jwks
// 参考 https://developer.okta.com/blog/2021/01/04/offline-jwt-validation-with-go#how-to-validate-a-jwt-token-in-go
func (r *JwtOmp) GetPublicKeys() {
	r.Rsakeys = make(map[string]*rsa.PublicKey)
	var body map[string]interface{}
	resp, _ := http.Get(r.PublishKeyUrl)
	json.NewDecoder(resp.Body).Decode(&body)
	for _, bodykey := range body["keys"].([]interface{}) {
		key := bodykey.(map[string]interface{})
		kid := key["kid"].(string)
		rsakey := new(rsa.PublicKey)
		number, _ := base64.RawURLEncoding.DecodeString(key["n"].(string))
		rsakey.N = new(big.Int).SetBytes(number)
		rsakey.E = 65537
		r.Rsakeys[kid] = rsakey
	}
}

// Verify 校验token
func (r *JwtOmp) Verify(c *gin.Context) (bool, error) {
	isValid := false
	errorMessage := ""
	tokenString := c.Request.Header.Get("Authorization")
	if strings.HasPrefix(tokenString, "Bearer ") {
		tokenString = strings.TrimPrefix(tokenString, "Bearer ")
		c.Set("Authorization", tokenString)
		token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
			return r.Rsakeys[token.Header["kid"].(string)], nil
		})

		if err != nil {
			errorMessage = err.Error()
		} else if !token.Valid {
			errorMessage = "Invalid token"
		} else if token.Header["alg"] == nil {
			errorMessage = "alg must be defined"
		} else if token.Claims.(jwt.MapClaims)["aud"] != "" {
			var slice reflect.Value = reflect.ValueOf(token.Claims.(jwt.MapClaims)["aud"])
			var firstValue string
			if slice.Kind() == reflect.Slice {
				firstValue = slice.Index(0).Interface().(string)
			} else if slice.Kind() == reflect.Array {
				firstValue = slice.Index(0).Interface().(string)
			} else {
				errorMessage = "Invalid aud"
			}

			if firstValue != "" && firstValue != r.Aud {
				errorMessage = "Invalid aud"
			} else {
				isValid = true
			}

		} else if !strings.Contains(token.Claims.(jwt.MapClaims)["iss"].(string), r.Iss) {
			errorMessage = "Invalid iss"
		} else {
			isValid = true
		}

		if !isValid {
			return isValid, errors.New(errorMessage)
		}
	} else {
		return false, errors.New("Unauthorized")
	}
	return isValid, nil
}

func (r *JwtOmp) ParserJwtToken(c *gin.Context) (bool, error) {
	r.GetPublicKeys()
	isValid, err := r.Verify(c)
	if isValid == false && err != nil {
		return false, err
	} else {
		return true, nil
	}
}
